// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef MAP_KEYS_HH
# define MAP_KEYS_HH

# include "signal.hh"
# include "bus.hh"
# include <fast/stretch_factor.hh>

namespace funky {

  template <typename BSample, typename T, typename Activate,
	    size_t MAX, size_t N>
  struct map_keys_rec;

  template <typename T, typename Activate,
	    size_t MAX, size_t N>
  struct map_keys_rec<std::bitset<MAX>, T, Activate, MAX, N>
    : public map_keys_rec<std::bitset<MAX>, T, Activate, MAX, N+1> {
  private:
    typedef map_keys_rec<std::bitset<MAX>, T, Activate, MAX, N+1> super;
    typedef typename signal_traits<T>::sample sample;
    typedef typename signal_traits<T>::duration duration;
    T t;

  public:
    map_keys_rec(map_keys_rec&& other)
      : super(std::move(other)),
	t(std::move(other.t)) {}

    map_keys_rec(const map_keys_rec& other)
      : super(other),
	t(other.t) {
    }

    explicit map_keys_rec(const T& t):
      super(t), t(t) {}

    map_keys_rec& operator=(const map_keys_rec& other) {
      return *this = map_keys_rec(other);
    }

    map_keys_rec& operator=(map_keys_rec&& other) {
      this->super::operator=(std::move(other));
      std::swap(t, other.t);
      return *this;
    }

    void inc(const duration& d, const std::bitset<MAX>& b) {
      this->super::inc(d, b);
      if (b[N])
	t += d*fast::stretch_factor<N>::value;
    }

    bus<sample, MAX> get(const std::bitset<MAX>& b) const {
      bus<sample, MAX> ret = this->super::get(b);
      if (b[N])
	ret[N] = *t;
      else
	ret[N] = sample();
      return std::move(ret);
    }
  };

  template <typename T, typename Activate, size_t MAX>
  struct map_keys_rec<std::bitset<MAX>, T, Activate, MAX, MAX> {
    typedef typename signal_traits<T>::sample sample;
    typedef typename signal_traits<T>::duration duration;
    explicit map_keys_rec(const T&) {}

    void inc(const duration&, const std::bitset<MAX>&) {
    }

    bus<sample, MAX> get(const std::bitset<MAX>&) const {
      return bus<sample, MAX>{};
    }
  };

  template <typename BSample, typename T, typename Activate,
	    size_t MAX = 128u, size_t N = 0u>
  struct map_keys: public map_keys_rec<BSample, T, Activate,
				       MAX, N> {
  private:
    typedef map_keys_rec<BSample, T, Activate,
			 MAX, N> super;
    typedef typename signal_traits<T>::sample sample;
    typedef typename signal_traits<T>::duration duration;
    Activate activate;

  public:
    map_keys(const T& t, Activate&& activate):
      super(t), activate(std::move(activate)) {}

    map_keys(const T& t, const Activate& activate):
      super(t), activate(activate) {}

    map_keys(const map_keys&) = default;
    map_keys(map_keys&& other) throw() :
      super(std::move(other)),
      activate(std::move(other.activate)) {}

    map_keys& operator=(const map_keys& other) {
      return *this = map_keys(other);
    }

    map_keys& operator=(map_keys&& other) throw() {
      this->super::operator=(other);
      std::swap(activate, other.activate);
      return *this;
    }

    map_keys& operator+=(const duration& d) {
      activate += d;
      this->super::inc(d, *activate);
      return *this;
    }

    bus<sample, MAX> operator*() const {
      return this->super::get(*activate);
    }
  };

  template <typename T>
  struct static_size {
    static const size_t size = T::size;
  };

  template <size_t N>
  struct static_size<std::bitset<N> > {
    static const size_t size = N;
  };

  template <typename BSample, typename Signal, typename Activate>
  struct signal_traits<map_keys<BSample, Signal, Activate> > {
    typedef bus<typename signal_traits<Signal>::sample,
		static_size<typename signal_traits<Activate>::sample>::size>
    sample;
    typedef typename signal_traits<Signal>::duration duration;
  };

  template <typename T, typename U,
	    typename RT = typename std::decay<T>::type,
	    typename RU = typename std::decay<U>::type,
	    typename = typename signal_traits<RT>::sample,
	    typename BSample = typename signal_traits<RU>::sample>
  map_keys<BSample, RT, RU> map(T&& t, U&& u) {
    return map_keys<BSample, RT, RU>{ std::forward<T>(t), std::forward<U>(u) };
  }

}

#endif
